package com.hyt.it.ogt.kq.service.admission.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.StreamProgress;
import cn.hutool.core.text.StrPool;
import cn.hutool.core.util.IdcardUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.hyt.common.apiInfor.ApiResponse;
import com.hyt.core.base.BaseEntity;
import com.hyt.core.base.BaseServiceImpl;
import com.hyt.core.constant.CommonConstant;
import com.hyt.core.util.FastJsonUtil;
import com.hyt.core.util.RegexUtils;
import com.hyt.core.util.RemoteUtil;
import com.hyt.core.util.UUIDUtils;
import com.hyt.exception.exception.BusinessLogicException;
import com.hyt.exception.exception.BusinessRequestException;
import com.hyt.exception.exception.InvalidRequestException;
import com.hyt.it.ogt.cj.model.entity.ProjectCollectInfo;
import com.hyt.it.ogt.cj.model.entity.ProjectCollectInfoOption;
import com.hyt.it.ogt.cj.model.entity.ProjectImageStandard;
import com.hyt.it.ogt.cj.service.IProjectCollectInfoOptionService;
import com.hyt.it.ogt.cj.service.IProjectCollectInfoService;
import com.hyt.it.ogt.cj.service.IProjectImageStandardService;
import com.hyt.it.ogt.kq.common.config.ConfigManager;
import com.hyt.it.ogt.kq.common.config.exception.KqException;
import com.hyt.it.ogt.kq.common.gov.constant.ResponseCode;
import com.hyt.it.ogt.kq.common.gov.enums.ATOperateTypeEnum;
import com.hyt.it.ogt.kq.common.gov.enums.CollectInfoEnum;
import com.hyt.it.ogt.kq.common.gov.enums.GovSexEnum;
import com.hyt.it.ogt.kq.common.gov.enums.admission.AdmissionSexEnum;
import com.hyt.it.ogt.kq.common.gov.enums.admission.TicketSubjectSettingEnum;
import com.hyt.it.ogt.kq.service.admission.*;
import com.hyt.it.ogt.kq.service.gov.feign.cloudefile.CloudFileClient;
import com.hyt.it.ogt.kq.service.gov.feign.ks.KsFallbackFactoryClient;
import com.hyt.it.ogt.kq.service.gov.feign.ks.model.*;
import com.hyt.it.ogt.kq.service.gov.mapper.admission.ExamineeInfoMapper;
import com.hyt.it.ogt.kq.service.gov.model.dto.admission.AdmissionTicketExamineesPhotoFileDTO;
import com.hyt.it.ogt.kq.service.gov.model.entity.*;
import com.hyt.it.ogt.kq.service.gov.model.param.admission.ExamineeInfoExportParam;
import com.hyt.it.ogt.kq.service.gov.model.param.admission.ExamineeInfoPageParam;
import com.hyt.it.ogt.kq.service.gov.model.param.admission.ExamineeTicketViewPageParam;
import com.hyt.it.ogt.kq.service.gov.model.vo.admission.AdmissionQueryVO;
import com.hyt.it.ogt.kq.service.gov.model.vo.admission.ExamineeInfoPageVO;
import com.hyt.it.ogt.kq.service.gov.model.vo.admission.ExamineeInfoVO;
import com.hyt.it.ogt.kq.service.gov.service.*;
import com.hyt.it.ogt.kq.service.gov.service.obs.CloudObsService;
import com.hyt.it.ogt.kq.service.model.entity.Candidate;
import com.hyt.it.ogt.kq.service.model.entity.CandidatePhoto;
import com.hyt.it.ogt.kq.service.model.entity.Subject;
import com.hyt.it.ogt.kq.service.model.entity.admission.*;
import com.hyt.loginfo.model.User;
import com.hyt.loginfo.service.IBaseLoginService;
import com.hyt.loginfo.util.HwCloudObsUrlReplaceUtil;
import com.hyt.progress.constants.ProgressConstant;
import com.hyt.progress.service.ProgressManager;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.StreamProgress;
import cn.hutool.core.text.StrPool;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;

import static com.hyt.it.ogt.kq.common.gov.constant.ResponseCode.ADMISSION_TICKET_INFO_CREATE_TICKET_SYNC_KW_PROJECT_COLLECT_CONFIG_ERROR;
import static com.hyt.it.ogt.kq.common.gov.constant.ResponseCode.ADMISSION_TICKET_INFO_CREATE_TICKET_VIRTUAL_ROOM_NOT_PUBLISH_ERROR;

/**
 * <p>
 * 准考证考生信息
 * </p>
 *
 * @author zhoumiao
 * @since 2023-01-10
 */
@Service
@Slf4j
public class ExamineeInfoServiceImpl extends BaseServiceImpl<ExamineeInfoMapper, ExamineeInfo> implements IExamineeInfoService {

    @Resource
    ConfigManager configManager;
    @Resource
    HwCloudObsUrlReplaceUtil hwCloudObsUrlReplaceUtil;
    @Resource
    CloudObsService cloudObsService;
    @Resource
    CloudFileClient cloudFileClient;
    @Resource
    private KsFallbackFactoryClient ksClient;
    @Resource
    private IProjectCollectInfoService iProjectCollectInfoService;
    @Resource
    private IProjectCollectInfoOptionService iProjectCollectInfoOptionService;
    @Resource
    private IProjectImageStandardService iProjectImageStandardService;
    @Resource
    private IAdmissionTicketInfoService iAdmissionTicketInfoService;
    @Resource
    private IAdmissionTicketTemplateService iAdmissionTicketTemplateService;
    @Resource
    private IAdmissionTicketTemplateVariableService iAdmissionTicketTemplateVariableService;
    @Resource
    private IExamineeInfoExtService iExamineeInfoExtService;
    @Resource
    private IExamineeInfoService iExamineeInfoService;
    @Resource
    private IExamineeOperateService iExamineeOperateService;
    @Resource
    private IExamineeTimesService iExamineeTimesService;
    @Resource
    IBaseLoginService loginService;
    @Resource
    private ICandidateService iCandidateService;
    @Resource
    private ICandidateInfoService iCandidateInfoService;
    @Resource
    private ITimeService iTimeService;
    @Resource
    private ITimeRoomCandidateService iTimeRoomCandidateService;
    @Resource
    private ExamineeInfoMapper examineeInfoMapper;
    @Resource
    private ITaskService iTaskService;
    @Resource
    private ICandidatePhotoService iCandidatePhotoService;
    @Resource
    private ITaskPlaceService iTaskPlaceService;
    @Resource
    private ITaskRoomService iTaskRoomService;
    @Resource
    private ITimeExamService iTimeExamService;
    @Resource
    private ISubjectService iSubjectService;

    @SuppressWarnings("all")
    @Override
    public IPage<ExamineeInfoPageVO> pageExamineeInfo(ExamineeInfoPageParam<?> examineeInfoPageParam) {
        AdmissionTicketInfo admissionTicketInfo = iAdmissionTicketInfoService.getById(examineeInfoPageParam.getAdmissionTicketInfoId());
        if (admissionTicketInfo == null) {
            BusinessRequestException.throwException(43140103, "未查询到准考证信息");
        }

        switch (TicketSubjectSettingEnum.getByCode(admissionTicketInfo.getSubjectSetting())) {
            case MORE:
                return pageExamineeInfoMulti((ExamineeInfoPageParam<ExamineeInfo>) examineeInfoPageParam, admissionTicketInfo);
            default:
                return pageExamineeInfoSingle((ExamineeInfoPageParam<ExamineeTimes>) examineeInfoPageParam, admissionTicketInfo);
        }
    }

    /**
     * 单科目准考证考生列表分页数据查询
     *
     * @param examineeInfoPageParam
     * @return
     */
    private IPage<ExamineeInfoPageVO> pageExamineeInfoSingle(ExamineeInfoPageParam<ExamineeTimes> examineeInfoPageParam, AdmissionTicketInfo admissionTicketInfo) {
        return getBaseMapper().pageExamineeInfoSingle(examineeInfoPageParam)
                // 转换响应实体类
                .convert(examineeInfo -> this.convertExamineeInfoPageVO(examineeInfo, admissionTicketInfo, null));
    }

    /**
     * 多科目准考证考生列表分页数据查询
     *
     * @param examineeInfoPageParam
     * @return
     */
    private IPage<ExamineeInfoPageVO> pageExamineeInfoMulti(ExamineeInfoPageParam<ExamineeInfo> examineeInfoPageParam, AdmissionTicketInfo admissionTicketInfo) {
        return this.lambdaQuery()
                .eq(BaseEntity::getDelFlag, Boolean.FALSE)
                .eq(ExamineeInfo::getAdmissionTicketInfoId, examineeInfoPageParam.getAdmissionTicketInfoId())
                .and(StrUtil.isNotBlank(examineeInfoPageParam.getSearchWord()),
                        wrapper -> wrapper.like(ExamineeInfo::getExamineeName, examineeInfoPageParam.getSearchWord())
                                .or()
                                .like(ExamineeInfo::getIdentificationNo, examineeInfoPageParam.getSearchWord()))
                .eq(ObjectUtil.isNotEmpty(examineeInfoPageParam.getGenerateStatus()), ExamineeInfo::getGenerateStatus, examineeInfoPageParam.getGenerateStatus())
                .page(examineeInfoPageParam)
                // 转换响应实体类
                .convert(examineeInfo -> this.convertExamineeInfoPageVO(examineeInfo, admissionTicketInfo, examineeInfoPageVO -> {
                    // 获取考生批次信息
                    List<ExamineeTimes> examineeTimes = iExamineeTimesService.getByExamineeId(examineeInfo.getId());
                    // 科目
                    String subjectNames = examineeTimes.stream()
                            .map(ExamineeTimes::getSubjectName)
                            .filter(StrUtil::isNotBlank)
                            .collect(Collectors.joining(StrPool.SLASH));
                    examineeInfoPageVO.setSubjectName(subjectNames);
                    // 批次
                    String timeNames = examineeTimes.stream()
                            .map(ExamineeTimes::getTimeName)
                            .collect(Collectors.joining(StrPool.SLASH));
                    examineeInfoPageVO.setTimeName(timeNames);
                }));
    }

    /**
     * 转换考生信息分页VO
     *
     * @param examineeInfo
     * @param admissionTicketInfo
     * @return
     */
    private ExamineeInfoPageVO convertExamineeInfoPageVO(ExamineeInfo examineeInfo, AdmissionTicketInfo admissionTicketInfo, Consumer<ExamineeInfoPageVO> consumer) {
        ExamineeInfoPageVO examineeInfoPageVO = BeanUtil.toBean(examineeInfo, ExamineeInfoPageVO.class);
        // 考试时间
        String examineStartTime = DateUtil.format(admissionTicketInfo.getBeginDate(), DatePattern.NORM_DATE_PATTERN);
        String examineEndTime = DateUtil.format(admissionTicketInfo.getEndDate(), DatePattern.NORM_DATE_PATTERN);
        String mergeExamineDate = StrUtil.format("{}至{}",
                Optional.ofNullable(examineStartTime)
                        .orElse(StrPool.DASHED),
                Optional.ofNullable(examineEndTime)
                        .orElse(StrPool.DASHED));
        examineeInfoPageVO.setExamTime(mergeExamineDate);
        // 准考证号
        Optional.ofNullable(examineeInfoPageVO.getAdmissionSerialNumber())
                .filter(StrUtil::isNotBlank)
                .map(data -> data.replace(StrPool.COMMA, StrPool.SLASH))
                .ifPresent(examineeInfoPageVO::setAdmissionSerialNumber);
        if (consumer != null) {
            consumer.accept(examineeInfoPageVO);
        }
        return examineeInfoPageVO;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public Boolean cancelGenerate(String examineeInfoId) {
        // 取消生成批次信息
        iExamineeTimesService.lambdaUpdate()
                .eq(ExamineeTimes::getExamineeId, examineeInfoId)
                .set(ExamineeTimes::getGenerateStatus, Boolean.FALSE)
                .set(ExamineeTimes::getGenerateData, null)
                .set(ExamineeTimes::getAdmissionTicketPdfUrl, null)
                .set(ExamineeTimes::getAdmissionTicketImgUrl, null)
                .set(BaseEntity::getCreateBy, loginService.getUserId())
                .set(BaseEntity::getUpdateDate, LocalDateTime.now())
                .update();
        // 取消生成考生信息
        return this.lambdaUpdate()
                .eq(BaseEntity::getId, examineeInfoId)
                .set(ExamineeInfo::getGenerateData, null)
                .set(ExamineeInfo::getGenerateStatus, Boolean.FALSE)
                .set(BaseEntity::getCreateBy, loginService.getUserId())
                .set(BaseEntity::getUpdateDate, LocalDateTime.now())
                .update();
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public Boolean batchCancelGenerate(String admissionTicketInfoId) {
        // 取消生成批次信息
        iExamineeTimesService.lambdaUpdate()
                .eq(ExamineeTimes::getAdmissionTicketInfoId, admissionTicketInfoId)
                .set(ExamineeTimes::getGenerateStatus, Boolean.FALSE)
                .set(ExamineeTimes::getGenerateData, null)
                .set(ExamineeTimes::getAdmissionTicketPdfUrl, null)
                .set(ExamineeTimes::getAdmissionTicketImgUrl, null)
                .set(BaseEntity::getCreateBy, loginService.getUserId())
                .set(BaseEntity::getUpdateDate, LocalDateTime.now())
                .update();
        // 取消生成考生信息
        return this.lambdaUpdate()
                .eq(ExamineeInfo::getAdmissionTicketInfoId, admissionTicketInfoId)
                .eq(ExamineeInfo::getGenerateStatus, Boolean.TRUE)
                .set(ExamineeInfo::getGenerateData, null)
                .set(ExamineeInfo::getGenerateStatus, Boolean.FALSE)
                .set(BaseEntity::getCreateBy, loginService.getUserId())
                .set(BaseEntity::getUpdateDate, LocalDateTime.now())
                .update();
    }

    /**
     * 同步虚拟考场考生和配置
     *
     * @param taskId
     * @param currentUser
     * @param admissionTicketTaskId
     */
    @Override
    public void syncGov(String taskId, String currentUser, String admissionTicketTaskId) {
        log.info("[准考证][创建or编辑][数据同步][虚拟考场]开始同步虚拟考场考生和配置：{} {} {}", taskId, currentUser, admissionTicketTaskId);

        Task task = iTaskService.getById(taskId);
        if (!Objects.equals(true, task.getPublish())) {
            log.error("[准考证][创建or编辑][数据同步][虚拟考场]虚拟考场未发布，禁止同步：{} {} {}", taskId, currentUser, admissionTicketTaskId);
            throw new KqException(ADMISSION_TICKET_INFO_CREATE_TICKET_VIRTUAL_ROOM_NOT_PUBLISH_ERROR);
        }

        // 同步考试时间
        iAdmissionTicketInfoService.updateBeginAndEndDate(admissionTicketTaskId, task.getBeginDate(), task.getEndDate());

        // 删除历史准考证学生
        List<String> examineeIds = listExaminneIds(taskId);
        LocalDateTime now = LocalDateTime.now();
        iExamineeInfoService.delete(currentUser, now, examineeIds);

        // 同步考生信息
        LambdaQueryWrapper<Candidate> candidateLambdaQueryWrapper = new LambdaQueryWrapper<>();
        candidateLambdaQueryWrapper.eq(Candidate::getTaskId, taskId)
                .eq(Candidate::getDelFlag, false)
                .orderByAsc(BaseEntity::getCreateDate);

        // 分页进行同步
        int current = 1, size = 1000;
        Page<Candidate> candidatePage = new Page<>(current, size);
        Page<Candidate> page = iCandidateService.page(candidatePage, candidateLambdaQueryWrapper);

        saveExamineeByGov(taskId, currentUser, admissionTicketTaskId, now, page.getRecords());
        ProgressManager.updateProgressCurrent(20L, null);
        long pages = candidatePage.getPages();
        while (++current <= pages) {
            candidatePage = new Page<>(current, size);
            page = iCandidateService.page(candidatePage, candidateLambdaQueryWrapper);
            saveExamineeByGov(taskId, currentUser, admissionTicketTaskId, now, page.getRecords());
            ProgressManager.stepProgressCurrent(current, (int) pages, 60L, 80L, null);
        }
        ProgressManager.updateProgressCurrent(80L, "考生同步完成");
    }

    /**
     * 批量保存gov学生到准考证
     *
     * @param taskId
     * @param currentUser
     * @param admissionTicketTaskId
     * @param now
     * @param candidates
     */
    private void saveExamineeByGov(String taskId, String currentUser, String admissionTicketTaskId, LocalDateTime now, List<Candidate> candidates) {
        if (candidates.isEmpty()) {
            return;
        }
        // 查询学生的配置值
        List<String> candidateIds = candidates.stream().map(BaseEntity::getId).collect(Collectors.toList());
        List<CandidateInfo> candidateInfoList = iCandidateInfoService.listCandidateInfo(taskId, candidateIds);
        Map<String, List<CandidateInfo>> studentInfosMap = candidateInfoList.stream().collect(Collectors.groupingBy(CandidateInfo::getCandidateId));

        // 这里考务的采集信息和报名公用一套，需要换算成报名id
        String bmProjectId = taskId;
        Task task = iTaskService.getById(taskId);
        if (null != task && StringUtils.isNotEmpty(task.getTaskBmId())) {
            bmProjectId = task.getTaskBmId();
        }
        // 查询项目的配置信息
        List<ProjectCollectInfo> projectCollectInfos = iProjectCollectInfoService.getProjectCollectInfosByPid(bmProjectId);
        List<ProjectCollectInfoOption> projectCollectInfoOptions = iProjectCollectInfoOptionService.queryByProjectId(bmProjectId);

        // 查询学生的批次信息
        List<Time> times = iTimeService.lambdaQuery().eq(BaseEntity::getDelFlag, false).eq(Time::getTaskId, taskId).orderByAsc(Time::getCreateDate).list();

        // 查询学生的科目信息
        Map<String, Subject> subjectMap = iSubjectService.lambdaQuery()
                .eq(BaseEntity::getDelFlag, false).eq(Subject::getTaskId, taskId).select(BaseEntity::getId, Subject::getName)
                .list().stream().collect(Collectors.toMap(BaseEntity::getId, Function.identity()));

        // 查询学生的科目和准考证信息
        List<TimeRoomCandidate> timeRoomCandidates = iTimeRoomCandidateService.lambdaQuery()
                .eq(BaseEntity::getDelFlag, false)
                .eq(TimeRoomCandidate::getTaskId, taskId)
                .in(TimeRoomCandidate::getCandidateId, candidateIds)
                .select(TimeRoomCandidate::getCandidateId, TimeRoomCandidate::getTimeId, TimeRoomCandidate::getAdmissionNum, TimeRoomCandidate::getSeatNum, TimeRoomCandidate::getRoomId, TimeRoomCandidate::getSubjectId)
                .list();
        Map<String, List<TimeRoomCandidate>> candidateTimeRoomMap = timeRoomCandidates.stream().collect(Collectors.groupingBy(TimeRoomCandidate::getCandidateId));

        // 数据结构： candidateId -> [(timeId -> timeRoomCandidate)]
        Map<String, Map<String, TimeRoomCandidate>> candidateTimeMap = new HashMap<>();
        candidateTimeRoomMap.forEach((candidateId, tmpTimeRoomCandidates) -> {
            Map<String, TimeRoomCandidate> timeMap = tmpTimeRoomCandidates.stream().collect(Collectors.toMap(TimeRoomCandidate::getTimeId, Function.identity()));
            candidateTimeMap.put(candidateId, timeMap);
        });

        boolean isBmImport = iTaskService.isBmImport(taskId);
//        // 查询学生的照片信息
//        Map<String, CandidatePhoto> candidatePhotoMap = Collections.emptyMap();
//        if (!isBmImport) {
//            // 非报名导入数据查询photo表
//            candidatePhotoMap = iCandidatePhotoService.lambdaQuery()
//                    .eq(CandidatePhoto::getDelFlag, false)
//                    .eq(CandidatePhoto::getTaskId, taskId)
//                    .in(CandidatePhoto::getCandidateId, candidateIds)
//                    .select(CandidatePhoto::getPhotoPath, CandidatePhoto::getCandidateId)
//                    .list().stream()
//                    .collect(Collectors.toMap(CandidatePhoto::getCandidateId, Function.identity()));
//        }

        // 查询学生的照片信息
        Map<String, CandidatePhoto> candidatePhotoMap =
                iCandidatePhotoService.lambdaQuery()
                        .eq(CandidatePhoto::getDelFlag, false)
                        .eq(CandidatePhoto::getTaskId, taskId)
                        .in(CandidatePhoto::getCandidateId, candidateIds)
                        .select(CandidatePhoto::getPhotoPath, CandidatePhoto::getCandidateId)
                        .list().stream()
                        .collect(Collectors.toMap(CandidatePhoto::getCandidateId, Function.identity()));

        // 考点信息
        Map<String, TaskPlace> taskPlaceMap = iTaskPlaceService.lambdaQuery()
                .eq(BaseEntity::getDelFlag, false).eq(TaskPlace::getTaskId, taskId)
                .select(BaseEntity::getId, TaskPlace::getName, TaskPlace::getAddress)
                .list().stream().collect(Collectors.toMap(BaseEntity::getId, Function.identity()));

        // 考场信息
        Map<String, TaskRoom> taskRoomMap = iTaskRoomService.lambdaQuery().eq(BaseEntity::getDelFlag, false).eq(TaskRoom::getTaskId, taskId)
                .select(BaseEntity::getId, TaskRoom::getName, TaskRoom::getAddress, TaskRoom::getPlaceId)
                .list().stream().collect(Collectors.toMap(BaseEntity::getId, Function.identity()));

        // 考试id信息
        Map<String, TimeExam> examCodeMap = iTimeExamService.lambdaQuery().eq(TimeExam::getTaskId, taskId).select(TimeExam::getTimeId, TimeExam::getExamCode)
                .list().stream().collect(Collectors.toMap(TimeExam::getTimeId, Function.identity()));

        // 新增准考证学生及其配置信息
        List<ExamineeInfo> examineeInfos = new ArrayList<>(candidates.size());
        List<ExamineeInfoExt> examineeInfoExts = new LinkedList<>();
        List<ExamineeTimes> examineeTimes = new LinkedList<>();
        for (Candidate candidate : candidates) {
            // 考生的科目信息
            Map<String, TimeRoomCandidate> timeMap = candidateTimeMap.getOrDefault(candidate.getId(), Collections.emptyMap());
            // 考生的配置值
            List<CandidateInfo> candidateInfos = studentInfosMap.getOrDefault(candidate.getId(), new LinkedList<>());
            // 考生的照片
            CandidatePhoto candidatePhoto = candidatePhotoMap.get(candidate.getId());
            ExamineeInfo examineeInfo = createExamineeInfoByGov(currentUser, admissionTicketTaskId, now, projectCollectInfos, candidateInfos, isBmImport, candidatePhoto, candidate, timeMap);
            examineeInfos.add(examineeInfo);
            for (Time time : times) {
                // 编排信息
                TimeRoomCandidate timeRoomCandidate = timeMap.get(time.getId());
                if (Objects.nonNull(timeRoomCandidate)) {
                    examineeTimes.add(createExamineeTimesByGov(examineeInfo, currentUser, now, time, timeRoomCandidate, taskPlaceMap, taskRoomMap, examCodeMap, subjectMap));
                }

                // 考生的配置项
                for (CandidateInfo candidateInfo : candidateInfos) {
                    ExamineeInfoExt examineeInfoExt = createExamineeInfoExtByGov(currentUser, now, examineeInfo, candidateInfo, projectCollectInfos, projectCollectInfoOptions);
                    examineeInfoExts.add(examineeInfoExt);
                }
            }
        }

        saveBatch(examineeInfos);
        iExamineeInfoExtService.saveBatch(examineeInfoExts);
        iExamineeTimesService.saveBatch(examineeTimes);
        log.info("[准考证][数据同步][虚拟考场]{} {} 虚拟考场同步新增的学生：考试={} 学生数量={}", currentUser, now, taskId, examineeInfos.size());
    }

    /**
     * 创建准考证科目信息
     *
     * @param examineeInfo
     * @param currentUser
     * @param now
     * @param time
     * @param timeRoomCandidate
     * @param taskPlaceMap
     * @param taskRoomMap
     * @param examCodeMap
     * @param subjectMap
     * @return
     */
    private ExamineeTimes createExamineeTimesByGov(ExamineeInfo examineeInfo, String currentUser, LocalDateTime now, Time time, TimeRoomCandidate timeRoomCandidate, Map<String, TaskPlace> taskPlaceMap, Map<String, TaskRoom> taskRoomMap, Map<String, TimeExam> examCodeMap, Map<String, Subject> subjectMap) {
        ExamineeTimes examineeTimes = new ExamineeTimes();
        examineeTimes.setId(UUIDUtils.newSortUUID());
        examineeTimes.setCreateBy(currentUser);
        examineeTimes.setCreateDate(now);
        examineeTimes.setCreateBy(currentUser);
        examineeTimes.setUpdateDate(now);

        examineeTimes.setExamineeId(examineeInfo.getId());
        examineeTimes.setAdmissionTicketInfoId(examineeInfo.getAdmissionTicketInfoId());
        examineeTimes.setAdmissionSerialNumber(timeRoomCandidate.getAdmissionNum());
        examineeTimes.setTimeId(time.getId());
        examineeTimes.setTimeName(time.getName());
        Subject subject = subjectMap.get(timeRoomCandidate.getSubjectId());
        examineeTimes.setSubjectName(subject.getName());
        examineeTimes.setExamStartTime(time.getStartTime());
        examineeTimes.setExamEndTime(time.getEndTime());
        examineeTimes.setExamTime(iExamineeTimesService.getExamTime(time.getStartTime(), time.getEndTime()));
        String roomId = timeRoomCandidate.getRoomId();
        TaskRoom taskRoom = taskRoomMap.get(roomId);
        examineeTimes.setRoomAddress(taskRoom.getAddress());
        examineeTimes.setRoomName(taskRoom.getName());

        String placeId = taskRoom.getPlaceId();
        TaskPlace taskPlace = taskPlaceMap.get(placeId);
        examineeTimes.setPlaceAddress(taskPlace.getAddress());
        examineeTimes.setPlaceName(taskPlace.getName());
        examineeTimes.setSeatNum(timeRoomCandidate.getSeatNum());

        TimeExam timeExam = examCodeMap.get(time.getId());
        examineeTimes.setExamId(timeExam.getExamCode());
        return examineeTimes;
    }

    /**
     * 根据政企考务学生配置信息创建准考证学生配置信息
     *
     * @param currentUser
     * @param now
     * @param examineeInfo
     * @param candidateInfo
     * @param projectCollectInfos
     * @param projectCollectInfoOptions
     * @return
     */
    private ExamineeInfoExt createExamineeInfoExtByGov(String currentUser, LocalDateTime now, ExamineeInfo examineeInfo, CandidateInfo candidateInfo, List<ProjectCollectInfo> projectCollectInfos, List<ProjectCollectInfoOption> projectCollectInfoOptions) {
        ExamineeInfoExt examineeInfoExt = new ExamineeInfoExt();
        examineeInfoExt.setId(UUIDUtils.newSortUUID());
        examineeInfoExt.setCreateDate(now);
        examineeInfoExt.setCreateBy(currentUser);
        examineeInfoExt.setUpdateDate(now);
        examineeInfoExt.setUpdateBy(currentUser);
        examineeInfoExt.setExamineeId(examineeInfo.getId());

        Map<String, List<ProjectCollectInfoOption>> projectCollectOptionMap = projectCollectInfoOptions.stream().collect(Collectors.groupingBy(ProjectCollectInfoOption::getProjectCollectInfoId));
        projectCollectInfos.stream()
                .filter(projectCollectInfo -> candidateInfo.getProjectCollectInfoId().equals(projectCollectInfo.getId()))
                .findAny()
                .ifPresent(projectCollectInfo -> {
                    String code = CollectInfoEnum.convertCode(projectCollectInfo.getComponentName(), projectCollectInfo.getTitle());
                    examineeInfoExt.setVariableCode(code);
                    examineeInfoExt.setVariableName(projectCollectInfo.getTitle());
                    String variableValue = iExamineeInfoExtService.getVariableValue(candidateInfo.getValue(), projectCollectOptionMap, projectCollectInfo);
                    examineeInfoExt.setVariableValue(variableValue);
                });
        return examineeInfoExt;
    }

    /**
     * 根据政企考务学生创建准考证学生信息
     *
     * @param currentUser
     * @param admissionTicketTaskId
     * @param now
     * @param projectCollectInfos
     * @param candidateInfos
     * @param isBmImport
     * @param candidatePhoto
     * @param candidate
     * @param timeMap
     * @return
     */
    private ExamineeInfo createExamineeInfoByGov(String currentUser, String admissionTicketTaskId, LocalDateTime now, List<ProjectCollectInfo> projectCollectInfos, List<CandidateInfo> candidateInfos, boolean isBmImport, CandidatePhoto candidatePhoto, Candidate candidate, Map<String, TimeRoomCandidate> timeMap) {
        ExamineeInfo examineeInfo = new ExamineeInfo();
        String examineeId = UUIDUtils.newSortUUID();
        // 设置数据库基础信息
        examineeInfo.setId(examineeId);
        examineeInfo.setCreateDate(now);
        examineeInfo.setCreateBy(currentUser);
        examineeInfo.setUpdateDate(now);
        examineeInfo.setUpdateBy(currentUser);

        examineeInfo.setAdmissionTicketInfoId(admissionTicketTaskId);
        if (isBmImport) {
            // 设置基本信息
            examineeInfo.setExamineeName(findCandidateCollectInfoValue(candidateInfos, projectCollectInfos, CollectInfoEnum.NAME));
            examineeInfo.setExamineeSex(findCandidateCollectInfoValue(candidateInfos, projectCollectInfos, CollectInfoEnum.SEX));
            //examineeInfo.setExamineePhoto(findCandidateCollectInfoValue(candidateInfos, projectCollectInfos, CollectInfoEnum.CERTIFICATE_PHOTO));

            // 证件类型
            Optional.ofNullable(findCandidateCollectInfoValue(candidateInfos, projectCollectInfos, CollectInfoEnum.CERTIFICATE_TYPE))
                    .ifPresent(value -> examineeInfo.setIdentificationType(Integer.valueOf(value)));
            examineeInfo.setIdentificationNo(findCandidateCollectInfoValue(candidateInfos, projectCollectInfos, CollectInfoEnum.ID_CARD));
            examineeInfo.setExamineePhone(findCandidateCollectInfoValue(candidateInfos, projectCollectInfos, CollectInfoEnum.PHONE));
        } else {
            // 设置基本信息
            examineeInfo.setExamineeName(candidate.getName());
            examineeInfo.setExamineeSex(convertGovSex(candidate.getSex()));

            // 证件类型
            examineeInfo.setIdentificationType(candidate.getIdType());
            examineeInfo.setIdentificationNo(candidate.getIdNumber());
            examineeInfo.setExamineePhone(candidate.getPhone());
        }
        // 考生的照片统一从主表里面取，存在报名没有照片在kw上传的场景，数据没有拉平之前全部从主表里面取
        examineeInfo.setExamineePhoto(candidatePhoto != null ? candidatePhoto.getPhotoPath() : null);
        String admissionNums = timeMap.values().stream().map(TimeRoomCandidate::getAdmissionNum).distinct().collect(Collectors.joining(StrPool.COMMA));
        examineeInfo.setAdmissionSerialNumber(admissionNums);
        return examineeInfo;
    }

    /**
     * 转换gov性别到准考证
     *
     * @param sex
     * @return
     */
    private String convertGovSex(Integer sex) {
        if (Objects.equals(sex, GovSexEnum.MAN.getCode())) {
            return AdmissionSexEnum.MAN.getCode();
        } else if (Objects.equals(sex, GovSexEnum.WOMAN.getCode())) {
            return AdmissionSexEnum.WOMAN.getCode();
        }
        return null;
    }

    /**
     * 通过配置枚举查询学生的配置值
     *
     * @param candidateInfos
     * @param projectCollectInfos
     * @param collectInfoEnum
     * @return
     */
    private String findCandidateCollectInfoValue(List<CandidateInfo> candidateInfos, List<ProjectCollectInfo> projectCollectInfos, CollectInfoEnum collectInfoEnum) {
        String collectInfoId = projectCollectInfos.stream()
                .filter(projectCollectInfo -> collectInfoEnum.getCode().equals(projectCollectInfo.getComponentName()))
                .findFirst()
                .orElse(new ProjectCollectInfo())
                .getId();
        if (Objects.isNull(collectInfoId)) {
            return null;
        } else {
            return candidateInfos.stream()
                    .filter(candidateInfo -> collectInfoId.equals(candidateInfo.getProjectCollectInfoId()))
                    .findFirst()
                    .orElse(new CandidateInfo())
                    .getValue();
        }
    }

    private List<String> listExaminneIds(String taskId) {
        AdmissionTicketInfo admissionTicketInfo = iAdmissionTicketInfoService.getByExamId(taskId);
        if (Objects.isNull(admissionTicketInfo)) {
            return new LinkedList<>();
        }
        return lambdaQuery()
                .eq(ExamineeInfo::getAdmissionTicketInfoId, admissionTicketInfo.getId())
                .eq(ExamineeInfo::getDelFlag, false)
                .select(BaseEntity::getId)
                .list()
                .stream().map(BaseEntity::getId)
                .collect(Collectors.toList());
    }

    /**
     * 同步无考场考生和配置
     *
     * @param examId
     * @param currentUser
     * @param admissionTaskId
     * @param deptId
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void syncKw(String examId, User currentUser, String admissionTaskId, String deptId) {

        // 查询考试配置项
        ApiResponse<KwExamCollectConfigVO> examCollectConfigVOApiResponse = ksClient.getExamCollectConfig(examId);
        Integer configVOApiResponseCode = examCollectConfigVOApiResponse.getCode();

        // 查询考生的配置项值
        int current = 1;
        int size = 500;
        ApiResponse<Page<KwExamCandidateVO>> pageApiResponse = ksClient.pageCandidateInfo(examId, current, size);
        Integer pageApiResponseCode = pageApiResponse.getCode();
        if (!Objects.equals(ResponseCode.SUCCESS.getCode(), configVOApiResponseCode) || !Objects.equals(ResponseCode.SUCCESS.getCode(), pageApiResponseCode)) {
            log.error("[准考证][创建or编辑][数据同步][无考场]同步考生配置：查询考试配置项失败：考试={}", examId);
            throw new KqException(ADMISSION_TICKET_INFO_CREATE_TICKET_SYNC_KW_PROJECT_COLLECT_CONFIG_ERROR);
        }

        LocalDateTime now = LocalDateTime.now();

        // 删除老的配置项
        iAdmissionTicketInfoService.removeCollect(examId);

        // 新增新的配置项
        KwExamCollectConfigVO kwExamCollectConfigVO = examCollectConfigVOApiResponse.getData();
        List<KwProjectCollectInfoVO> kwProjectCollectInfoVOS = kwExamCollectConfigVO.getKwProjectCollectInfoVOS();
        List<ProjectCollectInfo> projectCollectInfos = BeanUtil.copyToList(kwProjectCollectInfoVOS, ProjectCollectInfo.class);
        for (ProjectCollectInfo projectCollectInfo : projectCollectInfos) {
            projectCollectInfo.setCreateDate(now);
            projectCollectInfo.setUpdateDate(now);
        }
        iProjectCollectInfoService.saveBatch(projectCollectInfos);
        log.info("[准考证][创建or编辑][数据同步][无考场]{} {} 无考场同步新增的配置项：考试id={} 配置项选项={}", currentUser, now, examId, projectCollectInfos.stream().map(ProjectCollectInfo::getTitle).collect(Collectors.toList()));

        List<KwProjectCollectInfoOptionVO> kwProjectCollectInfoOptionVOS = kwExamCollectConfigVO.getKwProjectCollectInfoOptionVOS();
        List<ProjectCollectInfoOption> projectCollectInfoOptions = BeanUtil.copyToList(kwProjectCollectInfoOptionVOS, ProjectCollectInfoOption.class);
        for (ProjectCollectInfoOption projectCollectInfoOption : projectCollectInfoOptions) {
            projectCollectInfoOption.setCreateDate(now);
            projectCollectInfoOption.setUpdateDate(now);
        }
        iProjectCollectInfoOptionService.saveBatch(projectCollectInfoOptions);
        log.info("[准考证][创建or编辑][数据同步][无考场]{} {} 无考场同步新增的配置项选项：考试id={} 配置项选项={}", currentUser, now, examId, projectCollectInfoOptions.stream().map(ProjectCollectInfoOption::getOptionLabel).collect(Collectors.toList()));

        List<KwProjectImageStandardVO> kwProjectImageStandardVOS = kwExamCollectConfigVO.getKwProjectImageStandardVOS();
        List<ProjectImageStandard> projectImageStandards = BeanUtil.copyToList(kwProjectImageStandardVOS, ProjectImageStandard.class);
        for (ProjectImageStandard projectImageStandard : projectImageStandards) {
            projectImageStandard.setCreateDate(now);
            projectImageStandard.setUpdateDate(now);
        }
        iProjectImageStandardService.saveBatch(projectImageStandards);
        log.info("[准考证][创建or编辑][数据同步][无考场]{} {} 无考场同步新增的图片配置项：考试id={} 图片配置项={}", currentUser, now, examId, projectImageStandards.stream().map(BaseEntity::getId).collect(Collectors.toList()));


        // 删除准考证学生
        List<String> examineeIds = listExaminneIds(examId);
        iExamineeInfoService.delete(currentUser.getId(), now, examineeIds);

        // 新增准考证学生及其配置信息
        Page<KwExamCandidateVO> examCandidateVOPage = pageApiResponse.getData();
        List<KwExamCandidateVO> firstPageExamCandidate = examCandidateVOPage.getRecords();
        saveExamineeByKw(examId, currentUser.getId(), admissionTaskId, now, firstPageExamCandidate, projectCollectInfos, projectCollectInfoOptions);
        long pages = examCandidateVOPage.getPages();
        ProgressManager.updateProgressCurrent(20L, null);
        while (++current <= pages) {
            pageApiResponse = ksClient.pageCandidateInfo(examId, current, size);
            saveExamineeByKw(examId, currentUser.getId(), admissionTaskId, now, pageApiResponse.getData().getRecords(), projectCollectInfos, projectCollectInfoOptions);
            ProgressManager.stepProgressCurrent(current, (int) pages, 60L, 80L, null);
        }
        ProgressManager.updateProgressCurrent(80L, "考生同步完成");

        // 同步考试时间
        KwExamVO kwExamVO = ksClient.exam(currentUser.getOfficeid(), deptId, examId).getData();
        iAdmissionTicketInfoService.updateBeginAndEndDate(admissionTaskId, kwExamVO.getBeginDate(), kwExamVO.getEndDate());
    }

    @Transactional(rollbackFor = Exception.class)
    public List<ExamineeInfo> delete(String currentUser, LocalDateTime now, List<String> examineeIds) {
        if (examineeIds.isEmpty()) {
            return Collections.emptyList();
        }
        List<ExamineeInfo> oldExaminees = lambdaQuery()
                .in(ExamineeInfo::getId, examineeIds)
                .eq(BaseEntity::getDelFlag, false)
                .list();

        lambdaUpdate()
                .in(ExamineeInfo::getId, examineeIds)
                .eq(BaseEntity::getDelFlag, false)
                .set(BaseEntity::getUpdateBy, currentUser)
                .set(BaseEntity::getUpdateDate, now)
                .set(BaseEntity::getDelFlag, true)
                .update();

        iExamineeInfoExtService.lambdaUpdate()
                .in(ExamineeInfoExt::getExamineeId, examineeIds)
                .eq(ExamineeInfoExt::getDelFlag, false)
                .set(BaseEntity::getUpdateBy, currentUser)
                .set(BaseEntity::getUpdateDate, now)
                .set(BaseEntity::getDelFlag, true)
                .update();

        iExamineeTimesService.lambdaUpdate()
                .in(ExamineeTimes::getExamineeId, examineeIds)
                .eq(ExamineeTimes::getDelFlag, false)
                .set(BaseEntity::getUpdateBy, currentUser)
                .set(BaseEntity::getUpdateDate, now)
                .set(BaseEntity::getDelFlag, true)
                .update();
        log.info("{} {} 删除了学生={}", currentUser, now, examineeIds);
        return oldExaminees;
    }

    /**
     * 检查准考证任务是否存在
     *
     * @param zipFileUrl
     * @param admissionTicketInfoId
     * @return
     */
    @SneakyThrows
    @Override
    public void importAdmissionTicketPhoto(String zipFileUrl, String admissionTicketInfoId) {
        String fileSuffix = FileUtil.getSuffix(zipFileUrl);
        if (!StrUtil.equals(fileSuffix, ZIP_SUFFIX)) {
            BusinessLogicException.throwException(43330403, "上传的文件类型不匹配");
        }
        File uploadFile = FileUtil.createTempFile(StrPool.DOT + ZIP_SUFFIX, Boolean.TRUE);
        long fileSize = HttpUtil.downloadFile(zipFileUrl, uploadFile);
        if (fileSize == CommonConstant.ZERO) {
            BusinessLogicException.throwException(43330402, "请勿上传一个空文件");
        }
        if (fileSize > configManager.getHeadPhotoFileMaxSizeToByte()) {
            BusinessLogicException.throwException(ProgressConstant.RESPONSE_CODE_BUSINESS_ERROR, StrUtil.format("上传的文件大小不得超过{}MB", configManager.getHeadPhotoFileMaxSize()));
        }
        ProgressManager.updateProgressCurrent(5L, "文件接收成功");

        // 获取Zip包中的证件照文件组装实体
        List<AdmissionTicketExamineesPhotoFileDTO> examineesPhotoFileDTOS = getZipFiles(uploadFile);
        if (CollUtil.isEmpty(examineesPhotoFileDTOS)) {
            BusinessLogicException.throwException(43330405, "未解析出证件");
        }
        ProgressManager.updateProgressCurrent(10L, "考生证件照解析完毕");

        Map<String, String> idNoMappingPhotoUrl = checkCertificateExamineesPhotoData(examineesPhotoFileDTOS);
        ProgressManager.updateProgressCurrent(80L, "校验考生证件照文件完毕");

        Set<Map.Entry<String, String>> entries = idNoMappingPhotoUrl.entrySet();
        for (int i = 0; i < entries.size(); i++) {
            Map.Entry<String, String> entry = CollUtil.get(entries, i);
            String idNo = entry.getKey();
            String photoUrl = entry.getValue();
            Optional<ExamineeInfo> examineesInfoOpt = this.lambdaQuery()
                    .eq(ExamineeInfo::getAdmissionTicketInfoId, admissionTicketInfoId)
                    .eq(ExamineeInfo::getIdentificationNo, idNo)
                    .eq(BaseEntity::getDelFlag, Boolean.FALSE)
                    .oneOpt();
            if (!examineesInfoOpt.isPresent()) {
                BusinessLogicException.throwException(ProgressConstant.RESPONSE_CODE_BUSINESS_ERROR, StrUtil.format("证件号码为：{}的考生未匹配成功！", idNo));
            }
            ExamineeInfo examineesInfo = examineesInfoOpt.get();
            this.lambdaUpdate()
                    .set(ExamineeInfo::getExamineePhoto, photoUrl)
                    .set(BaseEntity::getUpdateBy, loginService.getUserId())
                    .set(BaseEntity::getUpdateDate, LocalDateTime.now())
                    .eq(BaseEntity::getId, examineesInfo.getId())
                    .update();
            ProgressManager.stepProgressCurrent(i, entries.size(), 20L, 99L, StrUtil.format("证件号码为{}的考生证件照进行入库", idNo));
        }
        ProgressManager.updateProgressCurrent(100L, "考生证件照上传成功");
    }

    /**
     * 获取zip文件中的头像文件
     *
     * @param importFile
     * @return
     * @throws IOException
     */
    @SneakyThrows
    private List<AdmissionTicketExamineesPhotoFileDTO> getZipFiles(File importFile) {
        try (ZipFile zipFile = new ZipFile(importFile, Charset.forName("GBK"))) {
            List<AdmissionTicketExamineesPhotoFileDTO> examineesPhotoFileDTOS = Lists.newArrayListWithExpectedSize(zipFile.size());
            Enumeration<? extends ZipEntry> entries = zipFile.entries();
            while (entries.hasMoreElements()) {
                ZipEntry zipEntry = entries.nextElement();
                if (!zipEntry.isDirectory()) {
                    String entryName = zipEntry.getName();
                    List<String> splitFileName = StrUtil.split(entryName, StrUtil.SLASH);
                    if (splitFileName.size() > CommonConstant.ONE) {
                        continue;
                    }

                    InputStream inputStream = zipFile.getInputStream(zipEntry);
                    BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
                    File file = FileUtil.writeFromStream(bufferedInputStream, FileUtil.createTempFile(StrPool.DOT + FileUtil.getSuffix(entryName), Boolean.TRUE));
                    AdmissionTicketExamineesPhotoFileDTO photoFileDTO = AdmissionTicketExamineesPhotoFileDTO.builder()
                            .idNo(FileUtil.getPrefix(entryName))
                            .photoFile(file)
                            .build();
                    examineesPhotoFileDTOS.add(photoFileDTO);
                }
            }
            return examineesPhotoFileDTOS;
        }
    }

    /**
     * 校验证件文件
     *
     * @param examineesPhotoFileDTOS
     * @return
     */
    private Map<String, String> checkCertificateExamineesPhotoData(List<AdmissionTicketExamineesPhotoFileDTO> examineesPhotoFileDTOS) {
        Map<String, Long> idNoMappingCount = examineesPhotoFileDTOS.stream()
                .peek(data -> {
                    String fileType = FileUtil.getSuffix(data.getPhotoFile());
                    if (!StrUtil.equals(fileType, FileUtil.getSuffix(IMAGE_SUFFIX))) {
                        BusinessLogicException.throwException(ProgressConstant.RESPONSE_CODE_BUSINESS_ERROR, StrUtil.format("证件号码为{}的考生照片格式不匹配，请上传jpg格式照片！", data.getIdNo()));
                    }
                })
                .map(AdmissionTicketExamineesPhotoFileDTO::getIdNo)
                .map(this::checkIdNoValidity)
                .collect(Collectors.groupingBy(idNo -> idNo, Collectors.counting()));
        idNoMappingCount.forEach((idNo, count) -> {
            if (count > CommonConstant.ONE) {
                BusinessLogicException.throwException(ProgressConstant.RESPONSE_CODE_BUSINESS_ERROR, StrUtil.format("证件号码为{}的考生照片存在多个，请调整！", idNo));
            }
        });
        ProgressManager.updateProgressCurrent(20L, "考生证件号码解析验证完毕");

        AtomicInteger atomicInteger = new AtomicInteger();
        return examineesPhotoFileDTOS.stream()
                .map(examineesPhotoFileDTO -> {
                    // 上传考生图片文件到华为云
                    String photoUrl = this.uploadExamineePhotoFile(examineesPhotoFileDTO);
                    log.debug("# 导入考生{}照片URL：{}", examineesPhotoFileDTO.getIdNo(), photoUrl);

                    ProgressManager.stepProgressCurrent(atomicInteger.getAndIncrement(), examineesPhotoFileDTOS.size(), 50L, 80L, StrUtil.format("正在上传{}考生照片", examineesPhotoFileDTO.getIdNo()));

                    return ImmutableMap.<String, String>builder()
                            .put(checkIdNoValidity(examineesPhotoFileDTO.getIdNo()), photoUrl)
                            .build();
                })
                .map(Map::entrySet)
                .flatMap(Collection::stream)
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
    }

    /**
     * 上传考生图片文件到华为云
     *
     * @param examineesPhotoFileDTO
     * @return
     */
    private String uploadExamineePhotoFile(AdmissionTicketExamineesPhotoFileDTO examineesPhotoFileDTO) {
        String filePath = "photo" + "/" + examineesPhotoFileDTO.getPhotoFile().getName();
        try {
            return cloudObsService.updateFile(filePath, examineesPhotoFileDTO.getPhotoFile());
        } catch (Exception e) {
            throw new InvalidRequestException(43330409, "考生照片上传失败", e);
        }
    }

    /**
     * 验证证件号码是否符合逻辑
     *
     * @param idNo
     * @return
     */
    private String checkIdNoValidity(String idNo) {
        List<String> allGroups = RegexUtils.getMatchers(ID_NO_REGEX, idNo);
        String realIdNo = StrUtil.join("", allGroups);
        return realIdNo;
    }

    /**
     * 无考场批量保存准考证学生信息
     *
     * @param examId
     * @param currentUser
     * @param admissionTicketTaskId
     * @param now
     * @param kwExamCandidateVOS
     * @param projectCollectInfos
     * @param projectCollectInfoOptions
     */
    private void saveExamineeByKw(String examId, String currentUser, String admissionTicketTaskId, LocalDateTime now, List<KwExamCandidateVO> kwExamCandidateVOS, List<ProjectCollectInfo> projectCollectInfos, List<ProjectCollectInfoOption> projectCollectInfoOptions) {
        List<ExamineeInfo> examineeInfos = new ArrayList<>(kwExamCandidateVOS.size());
        List<ExamineeInfoExt> examineeInfoExts = new LinkedList<>();
        List<ExamineeTimes> examineeTimes = new LinkedList<>();
        for (KwExamCandidateVO kwExamCandidateVO : kwExamCandidateVOS) {
            ExamineeInfo examineeInfo = new ExamineeInfo();
            String examineeId = UUIDUtils.newSortUUID();
            examineeInfo.setId(examineeId);
            examineeInfo.setCreateDate(now);
            examineeInfo.setCreateBy(currentUser);
            examineeInfo.setUpdateDate(now);
            examineeInfo.setUpdateBy(currentUser);

            examineeInfo.setAdmissionTicketInfoId(admissionTicketTaskId);
            examineeInfo.setExamineeName(kwExamCandidateVO.getName());
            examineeInfo.setExamineeSex(kwExamCandidateVO.getSex());
            examineeInfo.setAdmissionSerialNumber(kwExamCandidateVO.getAdmissionNumber());

            Integer cardType = castAndValidCardType(kwExamCandidateVO.getCardType(), kwExamCandidateVO.getName());
            examineeInfo.setIdentificationType(cardType);

            // 取第一张证件照
            List<String> photos = StrUtil.split(kwExamCandidateVO.getPhoto(), StrPool.COMMA);
            String photo = CollUtil.getFirst(photos);
            examineeInfo.setExamineePhoto(photo);
            examineeInfo.setIdentificationNo(kwExamCandidateVO.getCardNumber());
            examineeInfo.setExamineePhone(kwExamCandidateVO.getPhone());
            examineeInfos.add(examineeInfo);

            // 科目信息
            examineeTimes.add(createExamineeTimesByKw(examineeInfo, currentUser, now, kwExamCandidateVO));

            // 考生的配置项
            for (KwCandidateCollectInfoVO kwCandidateCollectInfoVO : kwExamCandidateVO.getKwCandidateCollectInfoVOS()) {
                createExamineeInfoExtByKw(currentUser, now, examineeId, kwCandidateCollectInfoVO, projectCollectInfos, projectCollectInfoOptions)
                        .ifPresent(examineeInfoExts::add);
            }
        }

        saveBatch(examineeInfos);
        iExamineeInfoExtService.saveBatch(examineeInfoExts);
        iExamineeTimesService.saveBatch(examineeTimes);
        log.info("[准考证][无考场][考生数据同步][新增]{} {} 无考场同步新增的学生：考试={} 学生={}", currentUser, now, examId,
                examineeInfos.stream()
                        .map(examinne -> examinne.getExamineeName() + "|" + examinne.getExamineePhone())
                        .collect(Collectors.toList()));
    }

    /**
     * 验证并转换证件类型
     *
     * @param cardType
     * @param name
     * @return
     */
    private Integer castAndValidCardType(String cardType, String name) {
        if (StrUtil.isBlank(cardType)) {
            return null;
        }
        boolean number = NumberUtil.isNumber(cardType);
        if (number) {
            return Integer.valueOf(cardType);
        }
        String format = StrUtil.format("{}考生的{}证件类型转换失败", name, cardType);
        throw new BusinessLogicException(ProgressConstant.RESPONSE_CODE_BUSINESS_ERROR, format);
    }

    /**
     * 创建科目信息
     *
     * @param examineeInfo
     * @param currentUser
     * @param now
     * @param kwExamCandidateVO
     * @return
     */
    private ExamineeTimes createExamineeTimesByKw(ExamineeInfo examineeInfo, String currentUser, LocalDateTime now, KwExamCandidateVO kwExamCandidateVO) {
        ExamineeTimes examineeTimes = new ExamineeTimes();
        examineeTimes.setId(UUIDUtils.newSortUUID());
        examineeTimes.setCreateBy(currentUser);
        examineeTimes.setCreateDate(now);
        examineeTimes.setUpdateBy(currentUser);
        examineeTimes.setUpdateDate(now);

        examineeTimes.setExamineeId(examineeInfo.getId());
        examineeTimes.setAdmissionTicketInfoId(examineeInfo.getAdmissionTicketInfoId());
        examineeTimes.setAdmissionSerialNumber(kwExamCandidateVO.getAdmissionNumber());
        examineeTimes.setTimeId(kwExamCandidateVO.getSubjectId());
        examineeTimes.setSubjectName(kwExamCandidateVO.getSubjectName());
        examineeTimes.setExamStartTime(kwExamCandidateVO.getBeginDate());
        examineeTimes.setExamEndTime(kwExamCandidateVO.getEndDate());
        examineeTimes.setExamTime(iExamineeTimesService.getExamTime(kwExamCandidateVO.getBeginDate(), kwExamCandidateVO.getEndDate()));
        examineeTimes.setExamId(kwExamCandidateVO.getRandomId());
        return examineeTimes;
    }

    /**
     * 通过kw创建准考证学生配置信息
     *
     * @param currentUser
     * @param now
     * @param examineeId
     * @param kwCandidateCollectInfoVO
     * @param projectCollectInfos
     * @param projectCollectInfoOptions
     * @return
     */
    private Optional<ExamineeInfoExt> createExamineeInfoExtByKw(String currentUser, LocalDateTime now, String examineeId, KwCandidateCollectInfoVO kwCandidateCollectInfoVO, List<ProjectCollectInfo> projectCollectInfos, List<ProjectCollectInfoOption> projectCollectInfoOptions) {
        ExamineeInfoExt examineeInfoExt = null;
        Map<String, List<ProjectCollectInfoOption>> projectCollectOptionMap = projectCollectInfoOptions.stream().collect(Collectors.groupingBy(ProjectCollectInfoOption::getProjectCollectInfoId));
        Optional<ProjectCollectInfo> collectInfoOptional = projectCollectInfos.stream()
                .filter(projectCollectInfo -> StrUtil.equals(kwCandidateCollectInfoVO.getProjectCollectInfoId(), projectCollectInfo.getId()))
                .findAny();
        if (collectInfoOptional.isPresent()) {
            examineeInfoExt = new ExamineeInfoExt();
            examineeInfoExt.setId(UUIDUtils.newSortUUID());
            examineeInfoExt.setCreateDate(now);
            examineeInfoExt.setCreateBy(currentUser);
            examineeInfoExt.setUpdateDate(now);
            examineeInfoExt.setUpdateBy(currentUser);
            examineeInfoExt.setExamineeId(examineeId);
            String code = CollectInfoEnum.convertCode(kwCandidateCollectInfoVO.getComponentName(), kwCandidateCollectInfoVO.getTitle());
            examineeInfoExt.setVariableCode(code);
            examineeInfoExt.setVariableName(kwCandidateCollectInfoVO.getTitle());
            String variableValue = iExamineeInfoExtService.getVariableValue(kwCandidateCollectInfoVO.getConfigValue(), projectCollectOptionMap, collectInfoOptional.get());
            examineeInfoExt.setVariableValue(variableValue);
        } else {
            log.error("[准考证][无考场][考生数据同步]考生配置项 {} {}在准考证不存在", examineeId, kwCandidateCollectInfoVO);
        }
        return Optional.ofNullable(examineeInfoExt);
    }

    /**
     * 无考场删除项目的配置信息
     *
     * @param examId
     * @param now
     * @param currentUser
     */
    private void deleteProjectCollectConfig(String examId, LocalDateTime now, String currentUser) {
        iProjectCollectInfoService.lambdaUpdate()
                .eq(ProjectCollectInfo::getProjectId, examId)
                .eq(BaseEntity::getDelFlag, false)
                .set(BaseEntity::getDelFlag, true)
                .set(BaseEntity::getUpdateBy, currentUser)
                .set(BaseEntity::getUpdateDate, now)
                .update();
        iProjectCollectInfoOptionService.lambdaUpdate()
                .eq(ProjectCollectInfoOption::getProjectId, examId)
                .eq(BaseEntity::getDelFlag, false)
                .set(BaseEntity::getDelFlag, true)
                .set(BaseEntity::getUpdateBy, currentUser)
                .set(BaseEntity::getUpdateDate, now)
                .update();
        iProjectImageStandardService.lambdaUpdate()
                .eq(ProjectImageStandard::getProjectId, examId)
                .eq(BaseEntity::getDelFlag, false)
                .set(BaseEntity::getDelFlag, true)
                .set(BaseEntity::getUpdateBy, currentUser)
                .set(BaseEntity::getUpdateDate, now)
                .update();
        log.info("{} {} 删除配置项 配置项选项 图片配置项：{}", currentUser, now, examId);
    }

    /**
     * 无考场新增单个考生
     *
     * @param kwExamCandidateVO
     * @param currentUser
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void insertByKw(KwExamCandidateVO kwExamCandidateVO, String currentUser) {
        AdmissionTicketInfo admissionTicketInfo = iAdmissionTicketInfoService.getByExamId(kwExamCandidateVO.getExamId());
        if (Objects.isNull(admissionTicketInfo)) {
            log.info("[准考证][无考场][考生数据同步][新增]无考场新增单个考生 准考证不存在：{} {}", kwExamCandidateVO, currentUser);
            return;
        }
        List<ProjectCollectInfo> projectCollectInfos = iProjectCollectInfoService.getProjectCollectInfosByPid(kwExamCandidateVO.getExamId());
        List<ProjectCollectInfoOption> projectCollectInfoOptions = iProjectCollectInfoOptionService.queryByProjectId(kwExamCandidateVO.getExamId());
        saveExamineeByKw(kwExamCandidateVO.getExamId(), currentUser, admissionTicketInfo.getId(), LocalDateTime.now(), Collections.singletonList(kwExamCandidateVO), projectCollectInfos, projectCollectInfoOptions);
    }

    /**
     * 无考场删除单个考生
     *
     * @param kwExamCandidateVO
     * @param currentUser
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void deleteByKw(KwExamCandidateVO kwExamCandidateVO, String currentUser) {
        AdmissionTicketInfo admissionTicketInfo;
        if (StrUtil.isNotBlank(kwExamCandidateVO.getTaskId())) {
            admissionTicketInfo = iAdmissionTicketInfoService.getByExamId(kwExamCandidateVO.getTaskId());
        } else {
            admissionTicketInfo = iAdmissionTicketInfoService.getByExamId(kwExamCandidateVO.getExamId());
        }
        if (Objects.isNull(admissionTicketInfo)) {
            return;
        }
        List<ExamineeTimes> examineeTimess = iExamineeTimesService.listByAdmissionAndNumber(admissionTicketInfo.getId(), kwExamCandidateVO.getAdmissionNumber());
        List<String> examineeIds = examineeTimess.stream().map(ExamineeTimes::getExamineeId).distinct().collect(Collectors.toList());
        if (examineeIds.isEmpty()) {
            log.error("[准考证][无考场][考生数据同步][删除]无考场删除学生：{}不存在", kwExamCandidateVO);
            return;
        }

        iExamineeInfoService.delete(currentUser, LocalDateTime.now(), examineeIds);
    }

    /**
     * 准考证是否已经发布
     *
     * @param admissionTicketInfo
     * @return
     */
    private boolean isPublished(AdmissionTicketInfo admissionTicketInfo) {
        return Objects.nonNull(admissionTicketInfo)
                && Objects.equals(true, admissionTicketInfo.getPublishStatus());
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updateByKw(KwExamCandidateVO kwExamCandidateVO, String currentUser) {
        boolean isVirtual = StrUtil.isNotBlank(kwExamCandidateVO.getTaskId());
        if (isVirtual) {
            updateVirtualByKw(kwExamCandidateVO, currentUser);
        } else {
            // 无考场更新直接删除旧数据，新增新数据
            iExamineeInfoService.deleteByKw(kwExamCandidateVO, currentUser);
            iExamineeInfoService.insertByKw(kwExamCandidateVO, currentUser);
        }
    }

    /**
     * 通过考试更新虚拟考场考生 更新准考证考生
     *
     * @param kwExamCandidateVO
     * @param currentUser
     */
    private void updateVirtualByKw(KwExamCandidateVO kwExamCandidateVO, String currentUser) {
        // 虚拟考场的更新 在原数据的基础上进行更新 因为考试不能传过来虚拟考场考生的全部信息
        // 无考场可以更改的信息：姓名 性别 证件类型 证件号码 出生日期 手机号 邮箱
        AdmissionTicketInfo admissionTicketInfo = iAdmissionTicketInfoService.getByExamId(kwExamCandidateVO.getTaskId());
        if (Objects.isNull(admissionTicketInfo)) {
            return;
        }
        // 通过准考证号和 准考证任务id查询准考证考生id
        iExamineeTimesService.listByAdmissionAndNumber(admissionTicketInfo.getId(), kwExamCandidateVO.getAdmissionNumber())
                .stream().findFirst().ifPresent(examineeTimes -> {
                    List<ProjectCollectInfo> projectCollectInfos = iProjectCollectInfoService.getProjectCollectInfosByPid(kwExamCandidateVO.getTaskId());
                    List<ProjectCollectInfoOption> projectCollectInfoOptions = iProjectCollectInfoOptionService.queryByProjectId(kwExamCandidateVO.getTaskId());
                    String examineeId = examineeTimes.getExamineeId();
                    // 更新examineeInfo信息 姓名 性别 证件类型 证件号码 手机号
                    lambdaUpdate().eq(BaseEntity::getId, examineeId)
                            .set(ExamineeInfo::getExamineeName, kwExamCandidateVO.getName())
                            .set(ExamineeInfo::getExamineeSex, kwExamCandidateVO.getSex())
                            .set(ExamineeInfo::getIdentificationType, kwExamCandidateVO.getCardType())
                            .set(ExamineeInfo::getIdentificationNo, kwExamCandidateVO.getCardNumber())
                            .set(ExamineeInfo::getExamineePhone, kwExamCandidateVO.getPhone())
                            .set(ExamineeInfo::getUpdateDate, LocalDateTime.now())
                            .set(ExamineeInfo::getUpdateBy, currentUser)
                            .update();
                    // 不用更新examineeTimes信息

                    // 如果是报名导入的考生 更新examineeExt信息 无考场更新的考生信息包含（姓名 性别 证件类型 证件号码 手机号 出生日期 邮箱）
                    if (iTaskService.isBmImport(kwExamCandidateVO.getTaskId())) {
                        List<ExamineeInfoExt> dbExamineeInfoExts = iExamineeInfoExtService.listByExaminee(examineeId);
                        Map<String, ExamineeInfoExt> dbCodeMap = dbExamineeInfoExts.stream().collect(Collectors.toMap(ExamineeInfoExt::getVariableCode, Function.identity()));

                        List<ExamineeInfoExt> newExamineeInfoExts = new LinkedList<>();
                        List<KwCandidateCollectInfoVO> kwCandidateCollectInfoVOS = kwExamCandidateVO.getKwCandidateCollectInfoVOS();
                        for (KwCandidateCollectInfoVO kwCandidateCollectInfoVO : kwCandidateCollectInfoVOS) {
                            createExamineeInfoExtByKw(currentUser, LocalDateTime.now(), examineeId, kwCandidateCollectInfoVO, projectCollectInfos, projectCollectInfoOptions)
                                    .ifPresent(newExamineeInfoExts::add);
                        }
                        Map<String, ExamineeInfoExt> newCodeMap = newExamineeInfoExts.stream().collect(Collectors.toMap(ExamineeInfoExt::getVariableCode, Function.identity()));

                        Set<String> dbCodes = dbCodeMap.keySet();
                        Set<String> newCodes = newCodeMap.keySet();

                        Set<String> rmCodes = dbCodes.stream().filter(e -> !newCodes.contains(e)).collect(Collectors.toSet());
                        Set<String> addCodes = newCodes.stream().filter(e -> !dbCodes.contains(e)).collect(Collectors.toSet());
                        Set<String> updateCodes = dbCodes.stream().filter(newCodes::contains).collect(Collectors.toSet());

                        // 删除
                        iExamineeInfoExtService.deleteByExamineeAndCodes(rmCodes, examineeId);
                        // 新增
                        List<ExamineeInfoExt> addExamineeInfoExt = new LinkedList<>();
                        for (String addCode : addCodes) {
                            addExamineeInfoExt.add(newCodeMap.get(addCode));
                        }
                        if (!addExamineeInfoExt.isEmpty()) {
                            iExamineeInfoExtService.saveBatch(addExamineeInfoExt);
                        }
                        // 更新
                        List<ExamineeInfoExt> updateExamineeInfoExt = new LinkedList<>();
                        for (String updateCode : updateCodes) {
                            updateExamineeInfoExt.add(newCodeMap.get(updateCode));
                        }
                        iExamineeInfoExtService.updateValueByExamineeAndCode(updateExamineeInfoExt, examineeId, currentUser);
                    }
                });
    }

    /**
     * 考务新增单个考生
     *
     * @param candidate
     * @param currentUser
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void insertByGov(Candidate candidate, String currentUser) {
        if (null == candidate) {
            return;
        }
        String taskId = candidate.getTaskId();
        AdmissionTicketInfo admissionTicketInfo = iAdmissionTicketInfoService.getByExamId(taskId);
        if (Objects.isNull(admissionTicketInfo)) {
            log.error("[准考证][数据同步][虚拟考场]政企考务{}不存在准考证任务", taskId);
            return;
        }
        if (isPublished(admissionTicketInfo)) {
            log.error("[准考证][数据同步][虚拟考场]准考证已经发布，禁止新增：{} {}", candidate, currentUser);
            return;
        }
        if (!iTaskService.isPublish(taskId)) {
            log.error("[准考证][数据同步][虚拟考场]虚拟考场未发布，禁止新增：{} {}", candidate, currentUser);
            return;
        }
        saveExamineeByGov(candidate.getTaskId(), currentUser, admissionTicketInfo.getId(), LocalDateTime.now(), Collections.singletonList(candidate));
    }

    /**
     * 考务删除单个考生
     *
     * @param candidate
     * @param currentUser
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public List<ExamineeInfo> deleteByGov(Candidate candidate, String currentUser) {
        AdmissionTicketInfo admissionTicketInfo = iAdmissionTicketInfoService.checkAndGetAdmissionTicketInfo(candidate.getTaskId());
        if(null == admissionTicketInfo) {
            return Collections.emptyList();
        }
        if (isPublished(admissionTicketInfo)) {
            log.info("准考证已经发布，禁止删除：{} {}", candidate, currentUser);
            return Collections.emptyList();
        }
        ExamineeInfo examineeInfo = lambdaQuery()
                .eq(ExamineeInfo::getAdmissionTicketInfoId, admissionTicketInfo.getId())
                .in(ExamineeInfo::getIdentificationNo, candidate.getIdNumber())
                .select(BaseEntity::getId)
                .last("limit 1").one();
        if (Objects.isNull(examineeInfo)) {
            log.error("虚拟考场删除学生：{}不存在", candidate);
            return Collections.emptyList();
        }
        return iExamineeInfoService.delete(currentUser, LocalDateTime.now(), Collections.singletonList(examineeInfo.getId()));
    }

    /**
     * 考务更新单个考生
     *
     * @param candidate
     * @param currentUser
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updateByGov(Candidate candidate, String currentUser) {
        String taskId = candidate.getTaskId();
        AdmissionTicketInfo admissionTicketInfo = iAdmissionTicketInfoService.getByExamId(taskId);
        if (Objects.isNull(admissionTicketInfo)) {
            log.info("政企考务{}不存在准考证任务", taskId);
            return;
        }
        if (isPublished(admissionTicketInfo)) {
            log.info("准考证已经发布，禁止删除：{} {}", candidate, currentUser);
            return;
        }

        // 删除学生本来的信息
        List<ExamineeInfo> examineeInfos = iExamineeInfoService.deleteByGov(candidate, currentUser);
        if (examineeInfos.isEmpty()) {
            return;
        }

        // 新增学生信息
        iExamineeInfoService.insertByGov(candidate, currentUser);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public AdmissionQueryVO queryByNameAndIdCardNo(String admissionTicketInfoId, String queryName, String queryNumber) {
        AdmissionQueryVO admissionQueryVO = new AdmissionQueryVO();
        // 获取准考证信息
        AdmissionTicketInfo admissionTicketInfo = iAdmissionTicketInfoService.getById(admissionTicketInfoId);
        if (null == admissionTicketInfo) {
            throw new KqException(43350104, "查询不到准考证信息");
        }
        if (!admissionTicketInfo.getPublishStatus()) {
            throw new KqException(43350102, "准考证未发布");
        }
        admissionQueryVO.setAdmissionTicketInfo(admissionTicketInfo);
        //组装查询语句
        QueryWrapper<ExamineeInfo> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda()
                .eq(ExamineeInfo::getAdmissionTicketInfoId, admissionTicketInfoId)
                .eq(ExamineeInfo::getExamineeName, queryName)
                .and(StrUtil.isNotBlank(queryNumber),
                        wrapper -> wrapper.like(ExamineeInfo::getAdmissionSerialNumber, queryNumber)
                                .or()
                                .eq(ExamineeInfo::getIdentificationNo, queryNumber))
                .eq(ExamineeInfo::getDelFlag, Boolean.FALSE).last("LIMIT 1");

        // 查询结果
        ExamineeInfo examineeInfo = getOne(queryWrapper);
        if (null == examineeInfo) {
            throw new KqException(43350103, "查询不到考生信息");
        }
        admissionQueryVO.setExamineeInfo(examineeInfo);
        //组装查询语句
        QueryWrapper<ExamineeTimes> queryExamineeTimesWrapper = new QueryWrapper<>();
        queryExamineeTimesWrapper.lambda()
                .eq(ExamineeTimes::getExamineeId, examineeInfo.getId())
                .eq(ExamineeTimes::getDelFlag, Boolean.FALSE);

        List<ExamineeTimes> examineeTimes = iExamineeTimesService.list(queryExamineeTimesWrapper);
        if (CollUtil.isEmpty(examineeTimes)) {
            throw new KqException(43350105, "查询不到考生科目信息");
        }
        List<ExamineeTimes> examineeTimesTemp = new ArrayList<>();
        // 是否全部都没有生成
        Boolean allNoCreate = Boolean.TRUE;
        for (ExamineeTimes examineeTimes2 : examineeTimes) {
            if (StringUtils.isNotBlank(examineeTimes2.getAdmissionTicketPdfUrl())) {
                allNoCreate = Boolean.FALSE;
                examineeTimesTemp.add(examineeTimes2);

            }
        }
        if (allNoCreate) {
            throw new KqException(43350106, "准考证未生成");
        }

        // 获取准考证的生成模式，如果是多科目生成则只取一条
        if (TicketSubjectSettingEnum.MORE.getCode().equals(admissionTicketInfo.getSubjectSetting())) {
            List<ExamineeTimes> examineeTimesTemp2 = new ArrayList<>();
            examineeTimesTemp2.add(examineeTimesTemp.get(0));
            admissionQueryVO.setExamineeTimes(examineeTimesTemp2);
        } else {
            admissionQueryVO.setExamineeTimes(examineeTimesTemp);
        }

        // 插入一条查看记录
        ExamineeOperate examineeOperate = new ExamineeOperate();
        examineeOperate.setAdmissionTicketId(admissionTicketInfoId);
        examineeOperate.setExamineeId(examineeInfo.getId());
        examineeOperate.setOperateType(ATOperateTypeEnum.VIEW.getType());
        examineeOperate.setOperateDate(LocalDateTime.now());
        iExamineeOperateService.save(examineeOperate);

        // 同时更新查看时间和查看次数
        Integer ticketViewTimes = examineeInfo.getTicketViewTimes();
        examineeInfo.setTicketViewTimes(ticketViewTimes + 1);
        examineeInfo.setLastTicketViewTime(LocalDateTime.now());
        saveOrUpdate(examineeInfo);
        return admissionQueryVO;
    }

    @SneakyThrows
    @Override
    @Transactional(rollbackFor = Exception.class)
    public String downloadByNameAndIdCardNo(HttpServletResponse response, String admissionTicketInfoId, String examineeId) {
        // 获取准考证信息
        AdmissionTicketInfo admissionTicketInfo = iAdmissionTicketInfoService.getById(admissionTicketInfoId);
        if (null == admissionTicketInfo) {
            return null;
        }
        // 组装查询语句
        QueryWrapper<ExamineeInfo> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(ExamineeInfo::getAdmissionTicketInfoId, admissionTicketInfoId).eq(ExamineeInfo::getId, examineeId).eq(ExamineeInfo::getDelFlag, Boolean.FALSE).last("LIMIT 1");

        // 查询结果
        ExamineeInfo examineeInfo = getOne(queryWrapper);
        if (null == examineeInfo) {
            return null;
        }
        // 获取批次生成结果
        List<ExamineeTimes> examineeTimes = iExamineeTimesService.lambdaQuery().eq(ExamineeTimes::getExamineeId, examineeId).eq(ExamineeTimes::getGenerateStatus, Boolean.TRUE)
                .eq(BaseEntity::getDelFlag, Boolean.FALSE).list();
        if (CollUtil.isEmpty(examineeTimes)) {
            return null;
        }
        List<ExamineeTimes> examineeTimesTemp = new ArrayList<>();
        // 获取准考证的生成模式，如果是多科目生成则只取一条
        if (TicketSubjectSettingEnum.MORE.getCode().equals(admissionTicketInfo.getSubjectSetting())) {
            examineeTimesTemp.add(examineeTimes.get(0));
        } else {
            examineeTimesTemp.addAll(examineeTimes);
        }


        List<String> examineeTimePdfUrls = examineeTimesTemp.stream().map(ExamineeTimes::getAdmissionTicketPdfUrl).filter(StrUtil::isNotBlank).collect(Collectors.toList());
        if (CollUtil.isEmpty(examineeTimePdfUrls)) {
            return null;
        }
        String downLoadUrl = "";
        if (examineeTimePdfUrls.size() > 1) {
            String fileName = admissionTicketInfo.getAdmissionTicketName() + ".zip";
            File tempZipFile = FileUtil.createTempFile(".zip", Boolean.TRUE);
            BufferedOutputStream bufferedOutputStream = FileUtil.getOutputStream(tempZipFile);
            ZipOutputStream zipOutputStream = new ZipOutputStream(bufferedOutputStream, StandardCharsets.UTF_8);
            for (ExamineeTimes examineeTime : examineeTimesTemp) {
                String pdfName = null;
                if (TicketSubjectSettingEnum.MORE.getCode().equals(admissionTicketInfo.getSubjectSetting())) {
                    pdfName = examineeInfo.getExamineeName() + "_" + examineeInfo.getIdentificationNo() + ".pdf";
                } else {
                    pdfName = examineeInfo.getExamineeName() + "_" + examineeInfo.getIdentificationNo() + "_" + examineeTime.getTimeName() + ".pdf";
                }
                ZipEntry pdfEntry = new ZipEntry(pdfName);
                zipOutputStream.putNextEntry(pdfEntry);
                String certificatePdfUrl = examineeTime.getAdmissionTicketPdfUrl();
                HttpUtil.download(certificatePdfUrl, zipOutputStream, Boolean.FALSE, new StreamProgress() {
                    @Override
                    public void start() {
                        log.debug("下载准考证开始下载pdf文件：{}", certificatePdfUrl);
                    }

                    @Override
                    public void progress(long total, long progressSize) {
                        NumberFormat numberFormat = new DecimalFormat("#,###");
                        log.debug("下载准考证下载pdf文件进行中：{}，文件总大小：{}，已经进行的大小：{}", certificatePdfUrl, numberFormat.format(total), numberFormat.format(progressSize));
                    }

                    @Override
                    public void finish() {
                        log.debug("下载准考证完成下载pdf文件：{}", certificatePdfUrl);
                    }
                });
            }
            // 节点流来自servlet，处理流不进行关闭
            IoUtil.flush(zipOutputStream);
            IoUtil.close(zipOutputStream);
            log.debug("批量下载准考证生成zip完成：{}", fileName);
            downLoadUrl = uploadAdmissionTicketZipFile(tempZipFile, fileName);
        } else {
            downLoadUrl = examineeTimePdfUrls.get(0);
        }
        // 同时记录下载记录
        ExamineeOperate examineeOperate = new ExamineeOperate();
        examineeOperate.setAdmissionTicketId(admissionTicketInfoId);
        examineeOperate.setExamineeId(examineeInfo.getId());
        examineeOperate.setOperateType(ATOperateTypeEnum.DOWNLOAD.getType());
        examineeOperate.setOperateDate(LocalDateTime.now());
        iExamineeOperateService.save(examineeOperate);

        // 同时更新查看时间和查看次数
        Integer ticketDownLoadTimes = examineeInfo.getTicketDownloadTimes();
        examineeInfo.setTicketDownloadTimes(ticketDownLoadTimes + 1);
        examineeInfo.setLastTicketDownloadTime(LocalDateTime.now());
        saveOrUpdate(examineeInfo);

        return downLoadUrl;
    }

    /**
     * 上传准考证ZIP文件到华为云
     *
     * @return
     */
    private String uploadAdmissionTicketZipFile(File zipFile, String fileName) {
        String filePath = "zip" + "/" + fileName;
        try {
            return cloudObsService.updateFile(filePath, zipFile);
        } catch (Exception e) {
            throw new InvalidRequestException(43340602, "准考证ZIP上传失败", e);
        }
    }


    @Override
    public <E extends IPage<ExamineeInfoVO>> E queryTicketViewList(String officeid, E pageParam, ExamineeTicketViewPageParam examineeTicketViewPageParam) {
        log.info("## queryTicketViewList officeid:{}, examineeTicketViewPageParam:{}", officeid, FastJsonUtil.getBeanToJson(examineeTicketViewPageParam));
        List<ExamineeInfoVO> list = examineeInfoMapper.queryTicketViewListPage(examineeTicketViewPageParam, pageParam.getCurrent(), pageParam.getSize());
        if (CollUtil.isNotEmpty(list)) {
            list.stream().forEach(examineeInfoVO ->
                    examineeInfoVO.setAdmissionSerialNumber(examineeInfoVO.getAdmissionSerialNumber().replace(StrPool.COMMA, "/"))
            );
        }
        pageParam.setRecords(list);
        Integer total = examineeInfoMapper.countTicketViewList(examineeTicketViewPageParam);
        pageParam.setTotal(null != total ? total : 0);
        return pageParam;
    }

    /**
     * 查询是否准考证任务是否已经生成准考证
     *
     * @param admissionTicketInfoId
     * @return
     */
    @Override
    public boolean isGeneratedTicket(String admissionTicketInfoId) {
        return lambdaQuery().eq(ExamineeInfo::getAdmissionTicketInfoId, admissionTicketInfoId)
                .eq(BaseEntity::getDelFlag, false)
                .eq(ExamineeInfo::getGenerateStatus, 1)
                .select(BaseEntity::getId).last("limit 1").one() != null;
    }

    /**
     * 通过准考证id删除考生数据
     *
     * @param admissionTicketInfoId
     * @param currentUser
     * @param deptId
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void deleteByAdmissionTicketId(String admissionTicketInfoId, User currentUser, String deptId) {
        List<String> examineeIds = lambdaQuery()
                .eq(BaseEntity::getDelFlag, false)
                .eq(ExamineeInfo::getAdmissionTicketInfoId, admissionTicketInfoId)
                .select(BaseEntity::getId)
                .list().stream().map(BaseEntity::getId).collect(Collectors.toList());
        if (examineeIds.isEmpty()) {
            return;
        }

        iExamineeInfoExtService.lambdaUpdate()
                .in(ExamineeInfoExt::getExamineeId, examineeIds)
                .set(BaseEntity::getDelFlag, true)
                .set(BaseEntity::getUpdateBy, currentUser.getId())
                .set(BaseEntity::getUpdateDate, LocalDateTime.now())
                .update();

        iExamineeTimesService.lambdaUpdate()
                .eq(ExamineeTimes::getAdmissionTicketInfoId, admissionTicketInfoId)
                .in(ExamineeTimes::getExamineeId, examineeIds)
                .set(BaseEntity::getDelFlag, true)
                .set(BaseEntity::getUpdateBy, currentUser.getId())
                .set(BaseEntity::getUpdateDate, LocalDateTime.now())
                .update();

        lambdaUpdate()
                .eq(ExamineeInfo::getAdmissionTicketInfoId, admissionTicketInfoId)
                .set(BaseEntity::getDelFlag, true)
                .set(BaseEntity::getUpdateBy, currentUser.getId())
                .set(BaseEntity::getUpdateDate, LocalDateTime.now())
                .update();
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public Boolean generateAdmissionTicket(String examineeInfoId) {
        return RemoteUtil.getOrThrows(() -> cloudFileClient.generateExaminee(examineeInfoId));
    }

    @Override
    public Boolean generateExamineeTimeAdmissionTicket(String examineeTimeId) {
        return RemoteUtil.getOrThrows(() -> cloudFileClient.generateTimes(examineeTimeId));
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public Boolean cancelExamineeTimeGenerate(String examineeTimeId) {
        ExamineeTimes examineeTimes = iExamineeTimesService.getById(examineeTimeId);
        if ( null == examineeTimes) {
            BusinessRequestException.throwException(43360202, "未查询到当前考生批次");
        }

        iExamineeTimesService.lambdaUpdate()
                .eq(BaseEntity::getId, examineeTimeId)
                .set(ExamineeTimes::getGenerateStatus, Boolean.FALSE)
                .set(ExamineeTimes::getGenerateData, null)
                .set(ExamineeTimes::getAdmissionTicketPdfUrl, null)
                .set(ExamineeTimes::getAdmissionTicketImgUrl, null)
                .set(ExamineeTimes::getUpdateBy, loginService.getUserId())
                .set(ExamineeTimes::getUpdateDate, LocalDateTime.now())
                .update();
        this.lambdaUpdate()
                .eq(BaseEntity::getId, examineeTimes.getExamineeId())
                .set(ExamineeInfo::getGenerateStatus, Boolean.FALSE)
                .set(ExamineeInfo::getUpdateBy, loginService.getUserId())
                .set(ExamineeInfo::getUpdateDate, LocalDateTime.now())
                .update();
        return Boolean.TRUE;
    }

    @Override
    public List<ExamineeInfoPageVO> exportAdmissionTicketExcelSingle(ExamineeInfoExportParam examineeInfoExportParam) {
        return getBaseMapper().exportAdmissionTicketExcelSingle(examineeInfoExportParam);
    }

    /**
     * 未生成准考证的数量
     *
     * @param admissionId
     * @return
     */
    @Override
    public Integer ticketNotGenerateCount(String admissionId) {
        return iExamineeInfoService.lambdaQuery()
                .eq(ExamineeInfo::getAdmissionTicketInfoId, admissionId)
                .eq(BaseEntity::getDelFlag, false)
                .eq(ExamineeInfo::getGenerateStatus, false)
                .count();
    }

    /**
     * 已经生成的准考证数量
     *
     * @param admissionId
     * @return
     */
    @Override
    public Integer ticketGeneratedCount(String admissionId) {
        return iExamineeInfoService.lambdaQuery()
                .eq(ExamineeInfo::getAdmissionTicketInfoId, admissionId)
                .eq(BaseEntity::getDelFlag, false)
                .eq(ExamineeInfo::getGenerateStatus, true)
                .count();
    }

    /**
     * 学生数量
     *
     * @param admissionId
     * @return
     */
    @Override
    public Integer examineeCount(String admissionId) {
        return iExamineeInfoService.lambdaQuery()
                .eq(ExamineeInfo::getAdmissionTicketInfoId, admissionId)
                .eq(BaseEntity::getDelFlag, false).count();
    }

    /**
     * 通过准考证任务id查询
     *
     * @param admissionId
     * @return
     */
    @Override
    public List<ExamineeInfo> listByAdmissionId(String admissionId) {
        return lambdaQuery().eq(ExamineeInfo::getAdmissionTicketInfoId, admissionId).eq(BaseEntity::getDelFlag, false).list();
    }

    @Override
    public String preview(String examineeInfoId) {
        User sysUser = loginService.getSysUser();
        if (null == sysUser) {
            BusinessLogicException.throwException(43330603, "预览准考证需要用户登录");
        }

        ExamineeInfo examineeInfo = this.getById(examineeInfoId);
        if (examineeInfo == null) {
            BusinessRequestException.throwException(43330502, "未查询到当前考生的信息");
        }

        List<ExamineeTimes> examineeTimes = Optional.ofNullable(iExamineeTimesService.getByExamineeId(examineeInfoId))
                .filter(CollUtil::isNotEmpty)
                .orElseThrow(() -> BusinessRequestException.getException(43330508, "未查询到当前所属的准考证考生批次信息"));

        String pdfUrl = examineeTimes.stream()
                .filter(ExamineeTimes::getGenerateStatus)
                .map(ExamineeTimes::getAdmissionTicketPdfUrl)
                .filter(StrUtil::isNotBlank)
                .findAny()
                .orElseThrow(() -> BusinessLogicException.getException(43330602, "当前考生还未生成准考证或不存在PDF文件"));


        String waterMark = (StrUtil.isNotBlank(sysUser.getPhone()) ? sysUser.getPhone() : System.currentTimeMillis()) + PRE_VIEW;
        String pdfWaterMarkUrl = RemoteUtil.getOrThrows(() -> cloudFileClient.waterMark(pdfUrl, waterMark));
        return hwCloudObsUrlReplaceUtil.replaceObsUrl(pdfWaterMarkUrl);
    }
}
